package nsfs

import (
	"GLibHac/fssystem"
	"github.com/pkg/errors"
)

type RomFsDirectory struct {
	parentFileSystem *RomFsFileSystem
	initialPosition  *FindPosition
	currentPosition  *FindPosition
	mode             OpenDirectoryMode
}

func (r *RomFsDirectory) Read(entryBuffer []*DirectoryEntry) (i int, err error) {
	tab := r.parentFileSystem.fileTable
	name := ""
	if r.mode.HasFlag(OpenDirectoryModeDirectory) {
		var dirEntry *DirectoryRomEntry
		for len(entryBuffer) == 0 || i < len(entryBuffer) {
			name, dirEntry, err = tab.FindNextDirectory(r.currentPosition)
			if err != nil {
				return i, err
			}
			if dirEntry == nil {
				break
			}
			r.currentPosition.NextDirectory = dirEntry.NextSibling
			if len(entryBuffer) > 0 {
				entryBuffer[i] = &DirectoryEntry{
					Name: name,
					Type: DirectoryEntryTypeDirectory,
					Size: 0,
				}
			}
			i++
		}
	}
	if r.mode.HasFlag(OpenDirectoryModeFile) {
		var nextFile *FileRomEntry
		for len(entryBuffer) == 0 || i < len(entryBuffer) {
			name, nextFile, err = tab.FindNextFile(r.currentPosition)
			if err != nil {
				return
			}
			if nextFile == nil {
				break
			}
			if len(entryBuffer) != 0 {
				entryBuffer[i] = &DirectoryEntry{
					Name: name,
					Type: DirectoryEntryTypeFile,
					Size: nextFile.Info.Length,
				}
			}
			i++
		}
	}
	return
}

func NewRomFsDirectory(fileSystem *RomFsFileSystem, position *FindPosition, mode OpenDirectoryMode) *RomFsDirectory {
	return &RomFsDirectory{
		parentFileSystem: fileSystem,
		initialPosition:  &FindPosition{position.NextDirectory, position.NextFile},
		currentPosition:  &FindPosition{position.NextDirectory, position.NextFile},
		mode:             mode,
	}
}

type RomfsHeader struct {
	HeaderSize          int64
	DirHashTableOffset  int64
	DirHashTableSize    int64
	DirMetaTableOffset  int64
	DirMetaTableSize    int64
	FileHashTableOffset int64
	FileHashTableSize   int64
	FileMetaTableOffset int64
	FileMetaTableSize   int64
	DataOffset          int64
}

func NewRomfsHeader(reader fssystem.IStorage) (header *RomfsHeader, err error) {
	header = &RomfsHeader{}
	header.HeaderSize, err = reader.ReadInt64()
	if err != nil {
		return
	}
	// Old pre-release romfs is exactly the same except the fields in the header are 32-bit instead of 64-bit
	readerFunc := reader.ReadInt64
	if header.HeaderSize == 0x28 {
		readerFunc = func() (int64, error) {
			readInt, err := reader.ReadInt()
			return int64(readInt), err
		}
	}
	header.DirHashTableOffset, err = readerFunc()
	if err != nil {
		return
	}
	header.DirHashTableSize, err = readerFunc()
	if err != nil {
		return
	}
	header.DirMetaTableOffset, err = readerFunc()
	if err != nil {
		return
	}
	header.DirMetaTableSize, err = readerFunc()
	if err != nil {
		return
	}
	header.FileHashTableOffset, err = readerFunc()
	if err != nil {
		return
	}
	header.FileHashTableSize, err = readerFunc()
	if err != nil {
		return
	}
	header.FileMetaTableOffset, err = readerFunc()
	if err != nil {
		return
	}
	header.FileMetaTableSize, err = readerFunc()
	if err != nil {
		return
	}
	header.DataOffset, err = readerFunc()
	if err != nil {
		return
	}
	return
}

type RomFsFileSystem struct {
	iFileSystemAbstract
	Header      *RomfsHeader
	baseStorage fssystem.IStorage
	fileTable   *HierarchicalRomFileTable
}

func (i *RomFsFileSystem) OpenFile(filePath string, mode OpenFileMode) (fssystem.IStorage, error) {
	info, err := i.fileTable.TryOpenFile(filePath)
	if err != nil {
		return nil, err
	}
	if mode != OpenFileModeRead {
		// RomFs files must be opened read-only.
		return nil, errors.Errorf("Cannot open file " + filePath + ", RomFs files must be opened read-only!")
	}
	return i.baseStorage.SliceRange(i.Header.DataOffset+info.Offset, info.Length), nil
}

func (i *RomFsFileSystem) OpenDirectory(path string, mode OpenDirectoryMode) (IDirectory, error) {
	position, err := i.fileTable.TryOpenDirectory(path)
	if err != nil {
		return nil, err
	}
	return NewRomFsDirectory(i, position, mode), nil
}

func NewRomFsFileSystem(storage fssystem.IStorage) (system *RomFsFileSystem, err error) {
	system = &RomFsFileSystem{
		baseStorage: storage,
	}
	system.Header, err = NewRomfsHeader(storage)
	dirHashTable := storage.SliceRange(system.Header.DirHashTableOffset, system.Header.DirHashTableSize)
	dirEntryTable := storage.SliceRange(system.Header.DirMetaTableOffset, system.Header.DirMetaTableSize)
	fileHashTable := storage.SliceRange(system.Header.FileHashTableOffset, system.Header.FileHashTableSize)
	fileEntryTable := storage.SliceRange(system.Header.FileMetaTableOffset, system.Header.FileMetaTableSize)
	system.fileTable, err = NewHierarchicalRomFileTable(dirHashTable, dirEntryTable, fileHashTable, fileEntryTable)
	system.openFileFunc = system.OpenFile
	system.openDirectory = system.OpenDirectory
	return
}
